/*-------------------------------------------------------------------------
 *
 * amutils.c
 *	  与索引访问方法相关的SQL级API。
 *
 * Copyright (c) 2016-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/amutils.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/amapi.h"
#include "access/htup_details.h"
#include "catalog/pg_class.h"
#include "catalog/pg_index.h"
#include "utils/builtins.h"
#include "utils/syscache.h"


/* 将字符串属性名称转换为枚举，以提高效率 */
struct am_propname
{
	const char *name;
	IndexAMProperty prop;
};

static const struct am_propname am_propnames[] =
{
	{
		"asc", AMPROP_ASC
	},
	{
		"desc", AMPROP_DESC
	},
	{
		"nulls_first", AMPROP_NULLS_FIRST
	},
	{
		"nulls_last", AMPROP_NULLS_LAST
	},
	{
		"orderable", AMPROP_ORDERABLE
	},
	{
		"distance_orderable", AMPROP_DISTANCE_ORDERABLE
	},
	{
		"returnable", AMPROP_RETURNABLE
	},
	{
		"search_array", AMPROP_SEARCH_ARRAY
	},
	{
		"search_nulls", AMPROP_SEARCH_NULLS
	},
	{
		"clusterable", AMPROP_CLUSTERABLE
	},
	{
		"index_scan", AMPROP_INDEX_SCAN
	},
	{
		"bitmap_scan", AMPROP_BITMAP_SCAN
	},
	{
		"backward_scan", AMPROP_BACKWARD_SCAN
	},
	{
		"can_order", AMPROP_CAN_ORDER
	},
	{
		"can_unique", AMPROP_CAN_UNIQUE
	},
	{
		"can_multi_col", AMPROP_CAN_MULTI_COL
	},
	{
		"can_exclude", AMPROP_CAN_EXCLUDE
	},
	{
		"can_include", AMPROP_CAN_INCLUDE
	},
};

static IndexAMProperty
fc_lookup_prop_name(const char *fc_name)
{
	int			fc_i;

	for (fc_i = 0; fc_i < lengthof(am_propnames); fc_i++)
	{
		if (pg_strcasecmp(am_propnames[fc_i].name, fc_name) == 0)
			return am_propnames[fc_i].prop;
	}

	/* 我们不抛出错误，以便AM可以定义自己的属性 */
	return AMPROP_UNKNOWN;
}

/*
 * 针对只是indoptions位测试的属性的通用代码。
 *
 * tuple：pg_index heap元组
 * attno：识别要测试indoptions的索引列。
 * guard：如果为假，强制返回布尔假结果（在调用者中节省代码）。
 * iopt_mask：有趣的indoption位的掩码。
 * iopt_expect：“真”结果的值（应为0或iopt_mask）。
 *
 * 返回false以指示NULL结果（对于“未知/不适用”），否则将*res设置为要返回的布尔值。
 */
static bool fc_test_indoption(HeapTuple fc_tuple, int fc_attno, bool fc_guard,
			   int16 fc_iopt_mask, int16 fc_iopt_expect,
			   bool *fc_res)
{
	Datum		fc_datum;
	bool		fc_isnull;
	int2vector *fc_indoption;
	int16		fc_indoption_val;

	if (!fc_guard)
	{
		*fc_res = false;
		return true;
	}

	fc_datum = SysCacheGetAttr(INDEXRELID, fc_tuple,
							Anum_pg_index_indoption, &fc_isnull);
	Assert(!fc_isnull);

	fc_indoption = ((int2vector *) DatumGetPointer(fc_datum));
	fc_indoption_val = fc_indoption->values[fc_attno - 1];

	*fc_res = (fc_indoption_val & fc_iopt_mask) == fc_iopt_expect;

	return true;
}


/*
 * 测试索引AM、索引或索引列的属性。
 *
 * 这是不同SQL级别函数的通用代码，因此amoid和index_oid参数是互斥的；如果需要，我们从index_oid查找amoid，或者如果没有给出索引oid，我们正在查看AM范围内的属性。
 */
static Datum fc_indexam_property(FunctionCallInfo fcinfo,
				 const char *fc_propname,
				 Oid fc_amoid, Oid fc_index_oid, int fc_attno)
{
	bool		fc_res = false;
	bool		fc_isnull = false;
	int			fc_natts = 0;
	IndexAMProperty fc_prop;
	IndexAmRoutine *fc_routine;

	/* 尝试将属性名称转换为枚举（如果未知则不报错） */
	fc_prop = fc_lookup_prop_name(fc_propname);

	/* 如果我们有索引OID，则查找AM，并获取列数 */
	if (OidIsValid(fc_index_oid))
	{
		HeapTuple	fc_tuple;
		Form_pg_class fc_rd_rel;

		Assert(!OidIsValid(fc_amoid));
		fc_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_index_oid));
		if (!HeapTupleIsValid(fc_tuple))
			PG_RETURN_NULL();
		fc_rd_rel = (Form_pg_class) GETSTRUCT(fc_tuple);
		if (fc_rd_rel->relkind != RELKIND_INDEX &&
			fc_rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
		{
			ReleaseSysCache(fc_tuple);
			PG_RETURN_NULL();
		}
		fc_amoid = fc_rd_rel->relam;
		fc_natts = fc_rd_rel->relnatts;
		ReleaseSysCache(fc_tuple);
	}

	/*
	 * 在这一点上，要么index_oid == InvalidOid，要么它是一个有效的索引OID。
	 * 此外，在此测试和下面的测试后，要么attno == 0表示索引范围或AM范围测试，或者它是有效索引中的有效列号。
	 */
	if (fc_attno < 0 || fc_attno > fc_natts)
		PG_RETURN_NULL();

	/*
	 * 获取AM信息。如果我们没有有效的AM OID，则返回NULL。
	 */
	fc_routine = GetIndexAmRoutineByAmId(fc_amoid, true);
	if (fc_routine == NULL)
		PG_RETURN_NULL();

	/*
	 * 如果有AM属性例程，给它一个机会来覆盖通用逻辑。如果它返回false，则继续。
	 */
	if (fc_routine->amproperty &&
		fc_routine->amproperty(fc_index_oid, fc_attno, fc_prop, fc_propname,
							&fc_res, &fc_isnull))
	{
		if (fc_isnull)
			PG_RETURN_NULL();
		PG_RETURN_BOOL(fc_res);
	}

	if (fc_attno > 0)
	{
		HeapTuple	fc_tuple;
		Form_pg_index fc_rd_index;
		bool		fc_iskey = true;

		/*
		 * 处理列级属性。其中许多需要pg_index行（我们也需要使用它来检查非关键属性），所以我们首先获取它。
		 */
		fc_tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(fc_index_oid));
		if (!HeapTupleIsValid(fc_tuple))
			PG_RETURN_NULL();
		fc_rd_index = (Form_pg_index) GETSTRUCT(fc_tuple);

		Assert(fc_index_oid == fc_rd_index->indexrelid);
		Assert(fc_attno > 0 && fc_attno <= fc_rd_index->indnatts);

		fc_isnull = true;

		/*
		 * 如果amcaninclude，我们可能正在查看非关键列的attno，对于这种情况，我们（通用地）假设大多数属性是null。
		 */
		if (fc_routine->amcaninclude
			&& fc_attno > fc_rd_index->indnkeyatts)
			fc_iskey = false;

		switch (fc_prop)
		{
			case AMPROP_ASC:
				if (fc_iskey &&
					fc_test_indoption(fc_tuple, fc_attno, fc_routine->amcanorder,
								   INDOPTION_DESC, 0, &fc_res))
					fc_isnull = false;
				break;

			case AMPROP_DESC:
				if (fc_iskey &&
					fc_test_indoption(fc_tuple, fc_attno, fc_routine->amcanorder,
								   INDOPTION_DESC, INDOPTION_DESC, &fc_res))
					fc_isnull = false;
				break;

			case AMPROP_NULLS_FIRST:
				if (fc_iskey &&
					fc_test_indoption(fc_tuple, fc_attno, fc_routine->amcanorder,
								   INDOPTION_NULLS_FIRST, INDOPTION_NULLS_FIRST, &fc_res))
					fc_isnull = false;
				break;

			case AMPROP_NULLS_LAST:
				if (fc_iskey &&
					fc_test_indoption(fc_tuple, fc_attno, fc_routine->amcanorder,
								   INDOPTION_NULLS_FIRST, 0, &fc_res))
					fc_isnull = false;
				break;

			case AMPROP_ORDERABLE:

				/*
				 * 通用假设是非关键列是不可排序的
				 */
				fc_res = fc_iskey ? fc_routine->amcanorder : false;
				fc_isnull = false;
				break;

			case AMPROP_DISTANCE_ORDERABLE:

				/*
				 * 一列是否距离可排序的条件实际上取决于AM（在撰写时，只有GiST完全支持它）。
				 * 规划器有自己的想法，基于它是否找到一个具有amoppurpose 'o'的运算符，但从仅索引列类型获得这一点似乎工作量很大。
				 * 所以，我们期望AM在其amproperty例程中处理这一点。通用结果是如果AM说它从不支持这一点，或者如果这是一个非关键列，则返回false，否则返回null（表示我们不知道）。
				 */
				if (!fc_iskey || !fc_routine->amcanorderbyop)
				{
					fc_res = false;
					fc_isnull = false;
				}
				break;

			case AMPROP_RETURNABLE:

				/* 请注意，我们忽略此属性的iskey */

				fc_isnull = false;
				fc_res = false;

				if (fc_routine->amcanreturn)
				{
					/*
					 * 如果可能，AM应该在其amproperty函数中处理此测试，而不打开rel。但是，如果它不这样做，这是通用的后备。
					 */
					Relation	fc_indexrel = index_open(fc_index_oid, AccessShareLock);

					fc_res = index_can_return(fc_indexrel, fc_attno);
					index_close(fc_indexrel, AccessShareLock);
				}
				break;

			case AMPROP_SEARCH_ARRAY:
				if (fc_iskey)
				{
					fc_res = fc_routine->amsearcharray;
					fc_isnull = false;
				}
				break;

			case AMPROP_SEARCH_NULLS:
				if (fc_iskey)
				{
					fc_res = fc_routine->amsearchnulls;
					fc_isnull = false;
				}
				break;

			default:
				break;
		}

		ReleaseSysCache(fc_tuple);

		if (!fc_isnull)
			PG_RETURN_BOOL(fc_res);
		PG_RETURN_NULL();
	}

	if (OidIsValid(fc_index_oid))
	{
		/*
		 * 处理索引级属性。目前，这仅取决于AM，但这可能不会永远如此，因此我们要求用户不仅命名索引，还命名AM。
		 */
		switch (fc_prop)
		{
			case AMPROP_CLUSTERABLE:
				PG_RETURN_BOOL(fc_routine->amclusterable);

			case AMPROP_INDEX_SCAN:
				PG_RETURN_BOOL(fc_routine->amgettuple ? true : false);

			case AMPROP_BITMAP_SCAN:
				PG_RETURN_BOOL(fc_routine->amgetbitmap ? true : false);

			case AMPROP_BACKWARD_SCAN:
				PG_RETURN_BOOL(fc_routine->amcanbackward);

			default:
				PG_RETURN_NULL();
		}
	}

	/*
	 * 处理AM级属性（控制在CREATE INDEX中可以说的内容）。
	 */
	switch (fc_prop)
	{
		case AMPROP_CAN_ORDER:
			PG_RETURN_BOOL(fc_routine->amcanorder);

		case AMPROP_CAN_UNIQUE:
			PG_RETURN_BOOL(fc_routine->amcanunique);

		case AMPROP_CAN_MULTI_COL:
			PG_RETURN_BOOL(fc_routine->amcanmulticol);

		case AMPROP_CAN_EXCLUDE:
			PG_RETURN_BOOL(fc_routine->amgettuple ? true : false);

		case AMPROP_CAN_INCLUDE:
			PG_RETURN_BOOL(fc_routine->amcaninclude);

		default:
			PG_RETURN_NULL();
	}
}

/*
 * 测试由AM OID指定的AM的属性
 */
Datum pg_indexam_has_property(PG_FUNCTION_ARGS)
{
	Oid			fc_amoid = PG_GETARG_OID(0);
	char	   *fc_propname = text_to_cstring(PG_GETARG_TEXT_PP(1));

	return fc_indexam_property(fcinfo, fc_propname, fc_amoid, InvalidOid, 0);
}

/*
 * 测试由索引OID指定的索引的属性
 */
Datum pg_index_has_property(PG_FUNCTION_ARGS)
{
	Oid			fc_relid = PG_GETARG_OID(0);
	char	   *fc_propname = text_to_cstring(PG_GETARG_TEXT_PP(1));

	return fc_indexam_property(fcinfo, fc_propname, InvalidOid, fc_relid, 0);
}

/*
 * 测试由索引OID和列号指定的索引列的属性
 */
Datum pg_index_column_has_property(PG_FUNCTION_ARGS)
{
	Oid			fc_relid = PG_GETARG_OID(0);
	int32		fc_attno = PG_GETARG_INT32(1);
	char	   *fc_propname = text_to_cstring(PG_GETARG_TEXT_PP(2));

	/* 立即拒绝 attno 0，以便 attno > 0 识别此情况 */
	if (fc_attno <= 0)
		PG_RETURN_NULL();

	return fc_indexam_property(fcinfo, fc_propname, InvalidOid, fc_relid, fc_attno);
}

/* 返回给定阶段的名称，用于给定 AM 的进度报告。 */
Datum pg_indexam_progress_phasename(PG_FUNCTION_ARGS)
{
	Oid			fc_amoid = PG_GETARG_OID(0);
	int32		fc_phasenum = PG_GETARG_INT32(1);
	IndexAmRoutine *fc_routine;
	char	   *fc_name;

	fc_routine = GetIndexAmRoutineByAmId(fc_amoid, true);
	if (fc_routine == NULL || !fc_routine->ambuildphasename)
		PG_RETURN_NULL();

	fc_name = fc_routine->ambuildphasename(fc_phasenum);
	if (!fc_name)
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(CStringGetTextDatum(fc_name));
}
